#ifndef NEURAL_NET_H
#define NEURAL_NET_H

#include <set>
#include <vector>

// Convention: member variables have a _ attached to the end.

using std::vector;
using std::set;

struct NeuralNetwork;

struct Weight {
  double value;
};

struct Node {
  Node() {
    // See the member variable descriptions below.
    fixed_weight_ = NULL;
    raw_value_ = 0;
    transformed_value_ = 0;
  }

  // Adds inputs to node.  If weight != NULL, then weight will be the weight of the input.  If weight
  // is NULL, then a new weight will be created.
  // We require network to be passed in for creation of a new Weight object if needed (by
  //   convention, all weights are owned by the network).
  void AddInput(Node* input, Weight* weight, NeuralNetwork* network);

  // A list of inputs and weights.
  // If inputs.size() == 0, then this is a raw input (that comes from the instance we are trying to
  // classify).
  vector<Node*> inputs_;
  vector<Weight*> weights_;

  // The fixed weight for this node (w_0).  This can be thought of as the constant in the linear
  // function of the node's inputs.  It is initialized to NULL because raw inputs do not need
  // these weights.
  Weight* fixed_weight_;

  // Nodes that take this node as an input
  vector<Node*> forward_neighbors_;
  vector<Weight*> forward_weights_;

  // The raw linear combination of inputs, i.e.
  // sum_{inputs i} weight_i * input_i + fixed_weight
  double raw_value_;

  // g(raw_value_)
  double transformed_value_;
};

struct Input {
  vector<double> values;
};

struct Target {
  vector<double> values;
};

struct NeuralNetwork {
  NeuralNetwork() {
    complete_ = false;
  }

  ~NeuralNetwork();

  // Nodes, split into different types.  All of these nodes are owned by the network.
  vector<Node*> inputs_;
  vector<Node*> hidden_nodes_;
  vector<Node*> outputs_;

  // Pointers into inputs, hidden_nodes, outputs.  NOTE: Nodes should be deleted only once (either
  // from inputs or from this set).
  set<Node*> node_set_;

  // The weights used in the network.  All of these weights are owned by the network.
  vector<Weight*> weights_;

  enum NodeType {
    INPUT,
    HIDDEN,
    OUTPUT
  };

  // Retrieves a new weight from the network.  Caller takes ownership of the returned object.
  Weight* GetNewWeight();

  // Add a node of node_type to the network.  Takes ownership of node.
  // NOTE: A node should not be modified after AddNode(node, ...) is called.
  // NOTE: All of node's inputs should already be in the network.  If not, an assert will be
  //   triggered.
  void AddNode(Node* node, NodeType node_type);

  // Should be called when all nodes have been added to the network.
  // At this point, we check that all inputs of a node were added before that node.
  // If this is not the case, an assert willbe triggered.
  void MarkAsComplete();

  // Utility functions for checking whether the network has been marked as complete.
  void CheckComplete();
  void CheckIncomplete();

  // Helper functions that we have defined for you.
  // The Sigmoid function
  static double Sigmoid(double value);
  // The derivative of the Sigmoid function
  static double SigmoidPrime(double value);

  // Functions you should define in neural-network-impl.cc
	void FeedForward(const Input& input, int printFlag=0);
  void Backprop(const Input& input, const Target& target, double learning_rate, int printFlag=0);
  void Train(const vector<Input>& inputs,
             const vector<Target>& targets,
             double learning_rate,
             int epochs,
						 int printFlag);

  // Retrieves the weights from the network.
  vector<double> GetWeights();

  // Initializes the network's weights using the given vector.
  void InitFromWeights(const vector<double>& weights);

 private:
  bool complete_;
};

// Function that calls delete on each pointer stored in a container of pointers.
template<class T>
void ClearVector(const T& container) {
  for (typename T::const_iterator it = container.begin(); it != container.end(); ++it) {
    delete *it;
  }
}

// Returns true if node_set contains node.
template<class T>
bool Contains(const T& node_set, typename T::key_type node) {
  return node_set.find(node) != node_set.end();
}

#endif
